package org.qiyu.live.user.provider.service.impl;

import com.alibaba.fastjson2.JSON;
import jakarta.annotation.Resource;
import org.apache.rocketmq.client.producer.MQProducer;
import org.apache.rocketmq.common.message.Message;
import org.idea.qiyu.live.framework.redis.starter.keys.UserProviderCacheKeyBuilder;
import org.qiyu.live.common.interfaces.topic.UserProviderTopicNames;
import org.qiyu.live.common.interfaces.utils.ConvertBeanUtils;
import org.qiyu.live.user.constants.CacheAsyncDeleteCode;
import org.qiyu.live.user.constants.UserTagFieldNameConstants;
import org.qiyu.live.user.constants.UserTagsEnum;
import org.qiyu.live.user.dto.UserCacheAsyncDeleteDTO;
import org.qiyu.live.user.dto.UserTagDTO;
import org.qiyu.live.user.provider.dao.mapper.IUserTagMapper;
import org.qiyu.live.user.provider.dao.po.UserTagPO;
import org.qiyu.live.user.provider.service.IUserTagService;
import org.qiyu.live.user.utils.TagInfoUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 作者：無言
 * 时间：2024/11/20 15:47
 * 描述：永无BUG
 */

@Service
public class UserTagServiceImpl implements IUserTagService {

    @Resource
    private IUserTagMapper userTagMapper;

    @Resource
    private UserProviderCacheKeyBuilder userProviderCacheKeyBuilder;

//    @Resource
//    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private RedisTemplate<String, UserTagDTO> redisTemplate;

    @Resource
    private MQProducer mqProducer;


    @Override
    public boolean setTag(Long userId, UserTagsEnum userTagsEnum) {
        //设置标签优化， 可以尝试更新，如果更新失败，则查询是否存在记录，不存在则执行新增操作
        boolean updateStatus = userTagMapper.setTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;
        if(updateStatus) {
            deleteUserTagDTOFromRedis(userId);
            return true;
        }

        //使用redis中setNx命令实现加锁，只允许一个线程进行下面的插入操作,解决分布式场景下同时插入的问题
        String setNxKey = userProviderCacheKeyBuilder.buildTagLockKey(userId);
        String setNxResult = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                //相当于命令  SET key "-1" NX EX 3
                return (String) connection.execute("set", keySerializer.serialize(setNxKey),
                        valueSerializer.serialize("-1"),
                        "NX".getBytes(StandardCharsets.UTF_8),
                        "EX".getBytes(StandardCharsets.UTF_8),
                        "3".getBytes(StandardCharsets.UTF_8));
            }
        });


        if(!"OK".equals(setNxResult)) {
            return false;
        }

        //在分布式场景下，有可能多个线程同时判断用户标签记录，同时进行插入，可能会出现错误，上面使用setNx命令加锁
        UserTagPO userTagPO = userTagMapper.selectById(userId);
        if (userTagPO != null) {
            return false;
        }
        userTagPO = new UserTagPO();
        userTagPO.setUserId(userId);
        userTagMapper.insert(userTagPO);
        updateStatus = userTagMapper.setTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;
        redisTemplate.delete(setNxKey);
        return updateStatus;
    }

    @Override
    public boolean cancelTag(Long userId, UserTagsEnum userTagsEnum) {
        boolean cancelStatus = userTagMapper.cancelTag(userId, userTagsEnum.getFieldName(), userTagsEnum.getTag()) > 0;
        if (!cancelStatus) {
            return false;
        }
        //删除redis,缓存，让读的时候，再进行redis缓存的同步，保持数据的一致性
        deleteUserTagDTOFromRedis(userId);
        return true;
    }



    @Override
    public boolean containTag(Long userId, UserTagsEnum userTagsEnum) {
        UserTagDTO userTagDTO = queryUserTagFromRedis(userId);
        if(userTagDTO == null) {
            return false;
        }

        String fieldName = userTagsEnum.getFieldName();
        return switch (fieldName) {
            case UserTagFieldNameConstants.TAG_INFO_01 ->
                    TagInfoUtils.isContain(userTagDTO.getTagInfo01(), userTagsEnum.getTag());
            case UserTagFieldNameConstants.TAG_INFO_02 ->
                    TagInfoUtils.isContain(userTagDTO.getTagInfo02(), userTagsEnum.getTag());
            case UserTagFieldNameConstants.TAG_INFO_03 ->
                    TagInfoUtils.isContain(userTagDTO.getTagInfo03(), userTagsEnum.getTag());
            default -> false;
        };
    }


    /**
     * 延迟双删、让读的时候再进行缓存的同步，保持数据的一致性
     * @param userId
     */
    private void deleteUserTagDTOFromRedis(Long userId) {
        redisTemplate.delete(userProviderCacheKeyBuilder.buildTagKey(userId));

        //发送消息到消息中间件，进行缓存的二次删除
        UserCacheAsyncDeleteDTO userCacheAsyncDeleteDTO = new UserCacheAsyncDeleteDTO();
        userCacheAsyncDeleteDTO.setCode(CacheAsyncDeleteCode.USER_TAG_DELETE.getCode());
        Map<String,Object> jsonParam = new HashMap<>();
        jsonParam.put("userId",userId);
        userCacheAsyncDeleteDTO.setJson(JSON.toJSONString(jsonParam));

        Message message = new Message();
        message.setTopic(UserProviderTopicNames.CACHE_ASYNC_DELETE_TOPIC);
        message.setBody(JSON.toJSONString(userCacheAsyncDeleteDTO).getBytes());
        //延迟一秒进行缓存的二次删除
        message.setDelayTimeLevel(1);
        try {
            mqProducer.send(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private UserTagDTO queryUserTagFromRedis(Long userId) {
        String userTagKey = userProviderCacheKeyBuilder.buildTagKey(userId);
        UserTagDTO userTagDTO = redisTemplate.opsForValue().get(userTagKey);
        if(userTagDTO != null) {
            return userTagDTO;
        }

        //查询数据库并设置到redis缓存中
        UserTagPO userTagPO = userTagMapper.selectById(userId);
        if(Objects.isNull(userTagPO)) {
            return null;
        }

        //设置缓存
        userTagDTO = ConvertBeanUtils.convert(userTagPO, UserTagDTO.class);
        redisTemplate.opsForValue().set(userTagKey, userTagDTO);
        redisTemplate.expire(userTagKey, 30, TimeUnit.MINUTES);
        return userTagDTO;

    }
}
